// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/web_contents/web_contents_android.h"

#include <stdint.h>

#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/command_line.h"
#include "base/containers/hash_tables.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "content/browser/accessibility/browser_accessibility_android.h"
#include "content/browser/accessibility/browser_accessibility_manager_android.h"
#include "content/browser/android/content_view_core_impl.h"
#include "content/browser/android/interstitial_page_delegate_android.h"
#include "content/browser/frame_host/interstitial_page_impl.h"
#include "content/browser/media/android/browser_media_player_manager.h"
#include "content/browser/media/android/media_web_contents_observer_android.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/web_contents/web_contents_view_android.h"
#include "content/common/devtools_messages.h"
#include "content/common/frame_messages.h"
#include "content/common/input_messages.h"
#include "content/common/view_messages.h"
#include "content/public/browser/android/app_web_message_port_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/message_port_provider.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "jni/WebContentsImpl_jni.h"
#include "net/android/network_library.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/android/overscroll_refresh_handler.h"
#include "ui/gfx/android/java_bitmap.h"
#include "ui/gfx/geometry/rect.h"

using base::android::AttachCurrentThread;
using base::android::ConvertJavaStringToUTF16;
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertUTF16ToJavaString;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaParamRef;
using base::android::JavaRef;
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;
using base::android::ToJavaIntArray;

namespace content {

namespace {

    // Track all WebContentsAndroid objects here so that we don't deserialize a
    // destroyed WebContents object.
    base::LazyInstance<base::hash_set<WebContentsAndroid*>>::Leaky
        g_allocated_web_contents_androids
        = LAZY_INSTANCE_INITIALIZER;

    void JavaScriptResultCallback(const ScopedJavaGlobalRef<jobject>& callback,
        const base::Value* result)
    {
        JNIEnv* env = base::android::AttachCurrentThread();
        std::string json;
        base::JSONWriter::Write(*result, &json);
        ScopedJavaLocalRef<jstring> j_json = ConvertUTF8ToJavaString(env, json);
        Java_WebContentsImpl_onEvaluateJavaScriptResult(env, j_json, callback);
    }

    struct AccessibilitySnapshotParams {
        AccessibilitySnapshotParams()
            : has_tree_data(false)
            , should_select_leaf_nodes(false)
        {
        }

        bool has_tree_data;
        // The current text selection within this tree, if any, expressed as the
        // node ID and character offset of the anchor (selection start) and focus
        // (selection end).
        int32_t sel_anchor_object_id;
        int32_t sel_anchor_offset;
        int32_t sel_focus_object_id;
        int32_t sel_focus_offset;
        // if the flag is true, mark the leaf node as selected.
        bool should_select_leaf_nodes;
    };

    ScopedJavaLocalRef<jobject> WalkAXTreeDepthFirst(
        JNIEnv* env,
        BrowserAccessibilityAndroid* node,
        const gfx::Rect& parent_rect,
        AccessibilitySnapshotParams* params)
    {
        ScopedJavaLocalRef<jstring> j_text = ConvertUTF16ToJavaString(env, node->GetText());
        ScopedJavaLocalRef<jstring> j_class = ConvertUTF8ToJavaString(env, node->GetClassName());
        // The style attributes exists and valid if size attribute exists. Otherwise,
        // they are not. Use a negative size information to indicate the existence
        // of style information.
        float size = -1.0;
        int color = 0;
        int bgcolor = 0;
        int text_style = 0;

        if (node->HasFloatAttribute(ui::AX_ATTR_FONT_SIZE)) {
            color = node->GetIntAttribute(ui::AX_ATTR_COLOR);
            bgcolor = node->GetIntAttribute(ui::AX_ATTR_BACKGROUND_COLOR);
            text_style = node->GetIntAttribute(ui::AX_ATTR_TEXT_STYLE);

            // The font size is just the computed style for that element; apply
            // transformations to get the actual pixel size.
            gfx::RectF text_size_rect(
                0, 0, 1, node->GetFloatAttribute(ui::AX_ATTR_FONT_SIZE));
            gfx::Rect scaled_text_size_rect = node->RelativeToAbsoluteBounds(
                text_size_rect, false);
            size = scaled_text_size_rect.height();
        }

        const gfx::Rect& absolute_rect = node->GetPageBoundsRect();
        gfx::Rect parent_relative_rect = absolute_rect;
        bool is_root = node->GetParent() == nullptr;
        if (!is_root) {
            parent_relative_rect.Offset(-parent_rect.OffsetFromOrigin());
        }
        ScopedJavaLocalRef<jobject> j_node = Java_WebContentsImpl_createAccessibilitySnapshotNode(
            env, parent_relative_rect.x(), parent_relative_rect.y(),
            absolute_rect.width(), absolute_rect.height(), is_root, j_text, color,
            bgcolor, size, text_style, j_class);

        if (params->has_tree_data && node->PlatformIsLeaf()) {
            int start_selection = 0;
            int end_selection = 0;
            if (params->sel_anchor_object_id == node->GetId()) {
                start_selection = params->sel_anchor_offset;
                params->should_select_leaf_nodes = true;
            }
            if (params->should_select_leaf_nodes)
                end_selection = node->GetText().length();

            if (params->sel_focus_object_id == node->GetId()) {
                end_selection = params->sel_focus_offset;
                params->should_select_leaf_nodes = false;
            }
            if (end_selection > 0)
                Java_WebContentsImpl_setAccessibilitySnapshotSelection(
                    env, j_node, start_selection, end_selection);
        }

        for (uint32_t i = 0; i < node->PlatformChildCount(); i++) {
            BrowserAccessibilityAndroid* child = static_cast<BrowserAccessibilityAndroid*>(
                node->PlatformGetChild(i));
            Java_WebContentsImpl_addAccessibilityNodeAsChild(
                env, j_node, WalkAXTreeDepthFirst(env, child, absolute_rect, params));
        }
        return j_node;
    }

    // Walks over the AXTreeUpdate and creates a light weight snapshot.
    void AXTreeSnapshotCallback(const ScopedJavaGlobalRef<jobject>& callback,
        const ui::AXTreeUpdate& result)
    {
        JNIEnv* env = base::android::AttachCurrentThread();
        if (result.nodes.empty()) {
            Java_WebContentsImpl_onAccessibilitySnapshot(env, nullptr, callback);
            return;
        }
        std::unique_ptr<BrowserAccessibilityManagerAndroid> manager(
            static_cast<BrowserAccessibilityManagerAndroid*>(
                BrowserAccessibilityManager::Create(result, nullptr)));
        manager->set_prune_tree_for_screen_reader(false);
        BrowserAccessibilityAndroid* root = static_cast<BrowserAccessibilityAndroid*>(manager->GetRoot());
        AccessibilitySnapshotParams params;
        if (result.has_tree_data) {
            params.has_tree_data = true;
            params.sel_anchor_object_id = result.tree_data.sel_anchor_object_id;
            params.sel_anchor_offset = result.tree_data.sel_anchor_offset;
            params.sel_focus_object_id = result.tree_data.sel_focus_object_id;
            params.sel_focus_offset = result.tree_data.sel_focus_offset;
        }
        gfx::Rect parent_rect;
        ScopedJavaLocalRef<jobject> j_root = WalkAXTreeDepthFirst(env, root, parent_rect, &params);
        Java_WebContentsImpl_onAccessibilitySnapshot(env, j_root, callback);
    }

} // namespace

// static
WebContents* WebContents::FromJavaWebContents(
    const JavaRef<jobject>& jweb_contents_android)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    if (jweb_contents_android.is_null())
        return NULL;

    WebContentsAndroid* web_contents_android = reinterpret_cast<WebContentsAndroid*>(
        Java_WebContentsImpl_getNativePointer(AttachCurrentThread(),
            jweb_contents_android));
    if (!web_contents_android)
        return NULL;
    return web_contents_android->web_contents();
}

// static
static void DestroyWebContents(JNIEnv* env,
    const JavaParamRef<jclass>& clazz,
    jlong jweb_contents_android_ptr)
{
    WebContentsAndroid* web_contents_android = reinterpret_cast<WebContentsAndroid*>(jweb_contents_android_ptr);
    if (!web_contents_android)
        return;

    WebContents* web_contents = web_contents_android->web_contents();
    if (!web_contents)
        return;

    delete web_contents;
}

// static
ScopedJavaLocalRef<jobject> FromNativePtr(JNIEnv* env,
    const JavaParamRef<jclass>& clazz,
    jlong web_contents_ptr)
{
    WebContentsAndroid* web_contents_android = reinterpret_cast<WebContentsAndroid*>(web_contents_ptr);

    if (!web_contents_android)
        return ScopedJavaLocalRef<jobject>();

    // Check to make sure this object hasn't been destroyed.
    if (g_allocated_web_contents_androids.Get().find(web_contents_android) == g_allocated_web_contents_androids.Get().end()) {
        return ScopedJavaLocalRef<jobject>();
    }

    return web_contents_android->GetJavaObject();
}

// static
bool WebContentsAndroid::Register(JNIEnv* env)
{
    return RegisterNativesImpl(env);
}

WebContentsAndroid::WebContentsAndroid(WebContentsImpl* web_contents)
    : web_contents_(web_contents)
    , navigation_controller_(&(web_contents->GetController()))
    , weak_factory_(this)
{
    g_allocated_web_contents_androids.Get().insert(this);
    JNIEnv* env = AttachCurrentThread();
    obj_.Reset(env,
        Java_WebContentsImpl_create(env, reinterpret_cast<intptr_t>(this),
            navigation_controller_.GetJavaObject())
            .obj());
    RendererPreferences* prefs = web_contents_->GetMutableRendererPrefs();
    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
    prefs->network_contry_iso = command_line->HasSwitch(switches::kNetworkCountryIso) ? command_line->GetSwitchValueASCII(switches::kNetworkCountryIso)
                                                                                      : net::android::GetTelephonyNetworkCountryIso();
}

WebContentsAndroid::~WebContentsAndroid()
{
    DCHECK(g_allocated_web_contents_androids.Get().find(this) != g_allocated_web_contents_androids.Get().end());
    g_allocated_web_contents_androids.Get().erase(this);
    Java_WebContentsImpl_clearNativePtr(AttachCurrentThread(), obj_);
}

base::android::ScopedJavaLocalRef<jobject>
WebContentsAndroid::GetJavaObject()
{
    return base::android::ScopedJavaLocalRef<jobject>(obj_);
}

ScopedJavaLocalRef<jstring> WebContentsAndroid::GetTitle(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj) const
{
    return base::android::ConvertUTF16ToJavaString(env,
        web_contents_->GetTitle());
}

ScopedJavaLocalRef<jstring> WebContentsAndroid::GetVisibleURL(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj) const
{
    return base::android::ConvertUTF8ToJavaString(
        env, web_contents_->GetVisibleURL().spec());
}

bool WebContentsAndroid::IsLoading(JNIEnv* env,
    const JavaParamRef<jobject>& obj) const
{
    return web_contents_->IsLoading();
}

bool WebContentsAndroid::IsLoadingToDifferentDocument(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj) const
{
    return web_contents_->IsLoadingToDifferentDocument();
}

void WebContentsAndroid::Stop(JNIEnv* env, const JavaParamRef<jobject>& obj)
{
    web_contents_->Stop();
}

void WebContentsAndroid::Cut(JNIEnv* env, const JavaParamRef<jobject>& obj)
{
    web_contents_->Cut();
}

void WebContentsAndroid::Copy(JNIEnv* env, const JavaParamRef<jobject>& obj)
{
    web_contents_->Copy();
}

void WebContentsAndroid::Paste(JNIEnv* env, const JavaParamRef<jobject>& obj)
{
    web_contents_->Paste();
}

void WebContentsAndroid::Replace(JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jstring>& jstr)
{
    web_contents_->Replace(base::android::ConvertJavaStringToUTF16(env, jstr));
}

void WebContentsAndroid::SelectAll(JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    web_contents_->SelectAll();
}

void WebContentsAndroid::Unselect(JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    web_contents_->Unselect();
}

RenderWidgetHostViewAndroid*
WebContentsAndroid::GetRenderWidgetHostViewAndroid()
{
    RenderWidgetHostView* rwhv = NULL;
    rwhv = web_contents_->GetRenderWidgetHostView();
    if (web_contents_->ShowingInterstitialPage()) {
        rwhv = web_contents_->GetInterstitialPage()
                   ->GetMainFrame()
                   ->GetRenderViewHost()
                   ->GetWidget()
                   ->GetView();
    }
    return static_cast<RenderWidgetHostViewAndroid*>(rwhv);
}

jint WebContentsAndroid::GetBackgroundColor(JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    RenderWidgetHostViewAndroid* rwhva = GetRenderWidgetHostViewAndroid();
    if (!rwhva)
        return SK_ColorWHITE;
    return rwhva->GetCachedBackgroundColor();
}

ScopedJavaLocalRef<jstring> WebContentsAndroid::GetURL(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj) const
{
    return ConvertUTF8ToJavaString(env, web_contents_->GetURL().spec());
}

ScopedJavaLocalRef<jstring> WebContentsAndroid::GetLastCommittedURL(
    JNIEnv* env,
    const JavaParamRef<jobject>&) const
{
    return ConvertUTF8ToJavaString(env,
        web_contents_->GetLastCommittedURL().spec());
}

jboolean WebContentsAndroid::IsIncognito(JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    return web_contents_->GetBrowserContext()->IsOffTheRecord();
}

void WebContentsAndroid::ResumeLoadingCreatedWebContents(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    web_contents_->ResumeLoadingCreatedWebContents();
}

void WebContentsAndroid::OnHide(JNIEnv* env, const JavaParamRef<jobject>& obj)
{
    web_contents_->WasHidden();
}

void WebContentsAndroid::OnShow(JNIEnv* env, const JavaParamRef<jobject>& obj)
{
    web_contents_->WasShown();
}

void WebContentsAndroid::SuspendAllMediaPlayers(
    JNIEnv* env,
    const JavaParamRef<jobject>& jobj)
{
    MediaWebContentsObserverAndroid::FromWebContents(web_contents_)
        ->SuspendAllMediaPlayers();
}

void WebContentsAndroid::SetAudioMuted(JNIEnv* env,
    const JavaParamRef<jobject>& jobj,
    jboolean mute)
{
    web_contents_->SetAudioMuted(mute);
}

void WebContentsAndroid::ShowInterstitialPage(JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jstring>& jurl,
    jlong delegate_ptr)
{
    GURL url(base::android::ConvertJavaStringToUTF8(env, jurl));
    InterstitialPageDelegateAndroid* delegate = reinterpret_cast<InterstitialPageDelegateAndroid*>(delegate_ptr);
    InterstitialPage* interstitial = InterstitialPage::Create(
        web_contents_, false, url, delegate);
    delegate->set_interstitial_page(interstitial);
    interstitial->Show();
}

jboolean WebContentsAndroid::IsShowingInterstitialPage(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    return web_contents_->ShowingInterstitialPage();
}

jboolean WebContentsAndroid::FocusLocationBarByDefault(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    return web_contents_->FocusLocationBarByDefault();
}

jboolean WebContentsAndroid::IsRenderWidgetHostViewReady(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    RenderWidgetHostViewAndroid* view = GetRenderWidgetHostViewAndroid();
    return view && view->HasValidFrame();
}

void WebContentsAndroid::ExitFullscreen(JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    web_contents_->ExitFullscreen(/*will_cause_resize=*/false);
}

void WebContentsAndroid::UpdateBrowserControlsState(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    bool enable_hiding,
    bool enable_showing,
    bool animate)
{
    RenderViewHost* host = web_contents_->GetRenderViewHost();
    if (!host)
        return;
    host->Send(new ViewMsg_UpdateBrowserControlsState(
        host->GetRoutingID(), enable_hiding, enable_showing, animate));
}

void WebContentsAndroid::ScrollFocusedEditableNodeIntoView(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    RenderViewHost* host = web_contents_->GetRenderViewHost();
    if (!host)
        return;
    host->Send(new InputMsg_ScrollFocusedEditableNodeIntoRect(
        host->GetRoutingID(), gfx::Rect()));
}

void WebContentsAndroid::SelectWordAroundCaret(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    RenderViewHost* host = web_contents_->GetRenderViewHost();
    if (!host)
        return;
    host->SelectWordAroundCaret();
}

void WebContentsAndroid::AdjustSelectionByCharacterOffset(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    jint start_adjust,
    jint end_adjust)
{
    web_contents_->AdjustSelectionByCharacterOffset(start_adjust, end_adjust);
}

void WebContentsAndroid::EvaluateJavaScript(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jstring>& script,
    const JavaParamRef<jobject>& callback)
{
    RenderViewHost* rvh = web_contents_->GetRenderViewHost();
    DCHECK(rvh);

    if (!rvh->IsRenderViewLive()) {
        if (!static_cast<WebContentsImpl*>(web_contents_)->CreateRenderViewForInitialEmptyDocument()) {
            LOG(ERROR) << "Failed to create RenderView in EvaluateJavaScript";
            return;
        }
    }

    if (!callback) {
        // No callback requested.
        web_contents_->GetMainFrame()->ExecuteJavaScript(
            ConvertJavaStringToUTF16(env, script));
        return;
    }

    // Secure the Java callback in a scoped object and give ownership of it to the
    // base::Callback.
    ScopedJavaGlobalRef<jobject> j_callback;
    j_callback.Reset(env, callback);
    RenderFrameHost::JavaScriptResultCallback js_callback = base::Bind(&JavaScriptResultCallback, j_callback);

    web_contents_->GetMainFrame()->ExecuteJavaScript(
        ConvertJavaStringToUTF16(env, script), js_callback);
}

void WebContentsAndroid::EvaluateJavaScriptForTests(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jstring>& script,
    const JavaParamRef<jobject>& callback)
{
    RenderViewHost* rvh = web_contents_->GetRenderViewHost();
    DCHECK(rvh);

    if (!rvh->IsRenderViewLive()) {
        if (!static_cast<WebContentsImpl*>(web_contents_)->CreateRenderViewForInitialEmptyDocument()) {
            LOG(ERROR) << "Failed to create RenderView in EvaluateJavaScriptForTests";
            return;
        }
    }

    if (!callback) {
        // No callback requested.
        web_contents_->GetMainFrame()->ExecuteJavaScriptForTests(
            ConvertJavaStringToUTF16(env, script));
        return;
    }

    // Secure the Java callback in a scoped object and give ownership of it to the
    // base::Callback.
    ScopedJavaGlobalRef<jobject> j_callback;
    j_callback.Reset(env, callback);
    RenderFrameHost::JavaScriptResultCallback js_callback = base::Bind(&JavaScriptResultCallback, j_callback);

    web_contents_->GetMainFrame()->ExecuteJavaScriptForTests(
        ConvertJavaStringToUTF16(env, script), js_callback);
}

void WebContentsAndroid::AddMessageToDevToolsConsole(
    JNIEnv* env,
    const JavaParamRef<jobject>& jobj,
    jint level,
    const JavaParamRef<jstring>& message)
{
    DCHECK_GE(level, 0);
    DCHECK_LE(level, CONSOLE_MESSAGE_LEVEL_LAST);

    web_contents_->GetMainFrame()->AddMessageToConsole(
        static_cast<ConsoleMessageLevel>(level),
        ConvertJavaStringToUTF8(env, message));
}

void WebContentsAndroid::PostMessageToFrame(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jstring>& jframe_name,
    const JavaParamRef<jstring>& jmessage,
    const JavaParamRef<jstring>& jsource_origin,
    const JavaParamRef<jstring>& jtarget_origin,
    const JavaParamRef<jintArray>& jsent_ports)
{
    base::string16 source_origin(ConvertJavaStringToUTF16(env, jsource_origin));
    base::string16 target_origin(ConvertJavaStringToUTF16(env, jtarget_origin));
    base::string16 message(ConvertJavaStringToUTF16(env, jmessage));
    std::vector<int> ports;

    if (!jsent_ports.is_null())
        base::android::JavaIntArrayToIntVector(env, jsent_ports, &ports);
    content::MessagePortProvider::PostMessageToFrame(
        web_contents_, source_origin, target_origin, message, ports);
}

void WebContentsAndroid::CreateMessageChannel(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jobjectArray>& ports)
{
    content::MessagePortProvider::GetAppWebMessagePortService()
        ->CreateMessageChannel(env, ports, web_contents_);
}

jboolean WebContentsAndroid::HasAccessedInitialDocument(
    JNIEnv* env,
    const JavaParamRef<jobject>& jobj)
{
    return static_cast<WebContentsImpl*>(web_contents_)->HasAccessedInitialDocument();
}

jint WebContentsAndroid::GetThemeColor(JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    return web_contents_->GetThemeColor();
}

void WebContentsAndroid::RequestAccessibilitySnapshot(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jobject>& callback)
{
    // Secure the Java callback in a scoped object and give ownership of it to the
    // base::Callback.
    ScopedJavaGlobalRef<jobject> j_callback;
    j_callback.Reset(env, callback);

    WebContentsImpl::AXTreeSnapshotCallback snapshot_callback = base::Bind(&AXTreeSnapshotCallback, j_callback);
    static_cast<WebContentsImpl*>(web_contents_)->RequestAXTreeSnapshot(snapshot_callback);
}

ScopedJavaLocalRef<jstring> WebContentsAndroid::GetEncoding(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj) const
{
    return base::android::ConvertUTF8ToJavaString(env,
        web_contents_->GetEncoding());
}

void WebContentsAndroid::SetOverscrollRefreshHandler(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& obj,
    const base::android::JavaParamRef<jobject>& overscroll_refresh_handler)
{
    WebContentsViewAndroid* view = static_cast<WebContentsViewAndroid*>(web_contents_->GetView());
    view->SetOverscrollRefreshHandler(
        base::MakeUnique<ui::OverscrollRefreshHandler>(
            overscroll_refresh_handler));
}

void WebContentsAndroid::GetContentBitmap(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jobject>& jcallback,
    const JavaParamRef<jobject>& color_type,
    jfloat scale,
    jfloat x,
    jfloat y,
    jfloat width,
    jfloat height)
{
    RenderWidgetHostViewAndroid* view = GetRenderWidgetHostViewAndroid();
    const ReadbackRequestCallback result_callback = base::Bind(
        &WebContentsAndroid::OnFinishGetContentBitmap, weak_factory_.GetWeakPtr(),
        ScopedJavaGlobalRef<jobject>(env, obj),
        ScopedJavaGlobalRef<jobject>(env, jcallback));
    SkColorType pref_color_type = gfx::ConvertToSkiaColorType(color_type);
    if (!view || pref_color_type == kUnknown_SkColorType) {
        result_callback.Run(SkBitmap(), READBACK_FAILED);
        return;
    }
    if (!view->IsSurfaceAvailableForCopy()) {
        result_callback.Run(SkBitmap(), READBACK_SURFACE_UNAVAILABLE);
        return;
    }
    view->GetScaledContentBitmap(scale,
        pref_color_type,
        gfx::Rect(x, y, width, height),
        result_callback);
}

void WebContentsAndroid::ReloadLoFiImages(JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    static_cast<WebContentsImpl*>(web_contents_)->ReloadLoFiImages();
}

int WebContentsAndroid::DownloadImage(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& obj,
    const base::android::JavaParamRef<jstring>& jurl,
    jboolean is_fav_icon,
    jint max_bitmap_size,
    jboolean bypass_cache,
    const base::android::JavaParamRef<jobject>& jcallback)
{
    GURL url(base::android::ConvertJavaStringToUTF8(env, jurl));
    return web_contents_->DownloadImage(
        url, is_fav_icon, max_bitmap_size, bypass_cache,
        base::Bind(&WebContentsAndroid::OnFinishDownloadImage,
            weak_factory_.GetWeakPtr(),
            ScopedJavaGlobalRef<jobject>(env, obj),
            ScopedJavaGlobalRef<jobject>(env, jcallback)));
}

void WebContentsAndroid::DismissTextHandles(
    JNIEnv* env,
    const base::android::JavaParamRef<jobject>& obj)
{
    RenderWidgetHostViewAndroid* view = GetRenderWidgetHostViewAndroid();
    if (view)
        view->DismissTextHandles();
}

void WebContentsAndroid::OnFinishGetContentBitmap(
    const JavaRef<jobject>& obj,
    const JavaRef<jobject>& callback,
    const SkBitmap& bitmap,
    ReadbackResponse response)
{
    JNIEnv* env = base::android::AttachCurrentThread();
    ScopedJavaLocalRef<jobject> java_bitmap;
    if (response == READBACK_SUCCESS)
        java_bitmap = gfx::ConvertToJavaBitmap(&bitmap);
    Java_WebContentsImpl_onGetContentBitmapFinished(env, obj, callback,
        java_bitmap, response);
}

void WebContentsAndroid::OnFinishDownloadImage(
    const JavaRef<jobject>& obj,
    const JavaRef<jobject>& callback,
    int id,
    int http_status_code,
    const GURL& url,
    const std::vector<SkBitmap>& bitmaps,
    const std::vector<gfx::Size>& sizes)
{
    JNIEnv* env = base::android::AttachCurrentThread();
    ScopedJavaLocalRef<jobject> jbitmaps = Java_WebContentsImpl_createBitmapList(env);
    ScopedJavaLocalRef<jobject> jsizes = Java_WebContentsImpl_createSizeList(env);
    ScopedJavaLocalRef<jstring> jurl = base::android::ConvertUTF8ToJavaString(env, url.spec());

    for (const SkBitmap& bitmap : bitmaps) {
        // WARNING: convering to java bitmaps results in duplicate memory
        // allocations, which increases the chance of OOMs if DownloadImage() is
        // misused.
        ScopedJavaLocalRef<jobject> jbitmap = gfx::ConvertToJavaBitmap(&bitmap);
        Java_WebContentsImpl_addToBitmapList(env, jbitmaps, jbitmap);
    }
    for (const gfx::Size& size : sizes) {
        Java_WebContentsImpl_createSizeAndAddToList(env, jsizes, size.width(),
            size.height());
    }
    Java_WebContentsImpl_onDownloadImageFinished(
        env, obj, callback, id, http_status_code, jurl, jbitmaps, jsizes);
}

void WebContentsAndroid::SetMediaSession(
    const ScopedJavaLocalRef<jobject>& j_media_session)
{
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_WebContentsImpl_setMediaSession(env, obj_, j_media_session);
}

} // namespace content
